/*
 * Created on 22.2.2007 by Mathew Stafurik
 */
package sk.sigp.eParser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * class responsible for evaluating expressions 
 * this has also hidden mechanism for handling inbound expressions with parenthesis
 * this class is also recursively creating itself when needed
 *   @author Mathew Stafurik
 *   @version 1.0
 */
@SuppressWarnings("unchecked")
class PairEvaluator {

    private VariableHolder _varHolder;
    private FunctionHolder _functionHolder;
    
	private List operators=null;
	private List tokenizedElements=null;
    
    public PairEvaluator() {        
        
    }
    
    /**
     * returns null if ch isnt token
     */
    private Character isCharToken(char ch) {
        String allElements=(String) getOperators().get(0);
        for (int i=0;i<allElements.length();i++) 
            if (allElements.charAt(i)==ch) return new Character(ch);
        return null;
    }
    
    /**
     * method responsible to parse string to desired token list
     */
    protected void tokenizeInput(String aStr) {                       
        getTokenizedElements().clear(); String wordGetter=""; String pInbound=""; boolean inbound=false; int inbCasey=0;
        for (int i=0;i<aStr.length();i++) {
            char cc=aStr.charAt(i);
            if (cc=='(' && !inbound) { inbound=true; inbCasey=0; pInbound+=wordGetter; wordGetter=""; }
            if (inbound) {                
                pInbound+=cc;
                //node when capturing inbound parethesis data
                if (cc=='(') inbCasey++;
                if (cc==')') {
                    inbCasey--; 
                    if (inbCasey==0) {
                        inbound=false;
                        getTokenizedElements().add(pInbound);
                        pInbound="";
                    }
                }
            } else {
                //node when capturing not bounded elements
                Character c=isCharToken(cc);
                if (c==null) //not token 
                    wordGetter+=cc;
                else {
                    if (wordGetter.length()>0) {
                        getTokenizedElements().add(wordGetter);
                        wordGetter="";                    
                    } else {
                        //getTokenizedElements().add("0");
                        wordGetter="";                                        
                    }
                    //add also token
                    getTokenizedElements().add(c);
                }
            }                
        }
        if (wordGetter.length()>0) {
            getTokenizedElements().add(wordGetter);
            wordGetter="";                    
        } 
    }    
    
    /**
     * returns operator's priority, higher number is higher priority
     */
    private int getOperatorPriority(Character operator) {
        Iterator i=getOperators().iterator();
        i.next(); //skip 
        int prio=0;
        while (i.hasNext()) {
            prio++;
            if (((String)i.next()).indexOf(operator.toString())>-1) return prio;  
        }
        return 0;
    }
    
    /**
     * checks if operator is viable for simplify
     */
    private boolean isOperatorViableForSimplify(Character operator) {
        Iterator i=getTokenizedElements().iterator(); int highestPriority=-1;
        while (i.hasNext()) {
            Object o=i.next();
            if ((o instanceof Character)&&(o!=operator)) {
                int oPrio=getOperatorPriority((Character)o);
                if (highestPriority<oPrio) highestPriority=oPrio;
            }
        }
        int oPrio=getOperatorPriority(operator);
        return oPrio>=highestPriority;
    }
    
    /**
     * static method tries to decide is string provided as parameter is valid double value
     */
    protected static boolean isStringDouble(String str) {
        if (str.length()<1) return false;
        switch (str.charAt(0)) {
        case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': break;
        default: return false;
        } 
        boolean foundDot=false;
        for (int i=1;i<str.length();i++) {
            char c=str.charAt(i);
            if (c=='.') {
                if (foundDot) return false;
                foundDot=true;
            }
            switch (c) {
            case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':case '.': break;
            default: return false;            
            }
        }
        return true;
    }
        
    /**
     * function responsible for converting single object to value
     */
    public static Double convertElementToValue(Object object,FunctionHolder fh,VariableHolder vh) {
        Double exportNumber=null;
        if (object instanceof String) {
            String aft=(String)object;            
            char a1=aft.charAt(0);
            switch (a1) {
                case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
                    //number
                    if (isStringDouble(aft))
                        exportNumber=new Double(Double.valueOf(aft).doubleValue());
                    else {
                        PairEvaluator pe=new PairEvaluator(); pe.setVarHolder(vh); pe.setFunctionHolder(fh);
                        exportNumber=pe.solvePairs(aft);
                    }
                    break;
                case '(':
                    //tricky recursive part - inbound parenthesis data
                    aft=aft.substring(1,aft.length()-1); //erase bulk data
                    PairEvaluator pe=new PairEvaluator(); pe.setVarHolder(vh); pe.setFunctionHolder(fh);
                    exportNumber=pe.solvePairs(aft);
                    break;
                default:
                    if (aft.indexOf("(")>-1) {
                        //function
                        if (fh!=null) 
                            exportNumber=fh.executeFunction(aft);
                        if (exportNumber==null) exportNumber=new Double(0); //if its still null, replace with 0 to make things work
                    } else {
                        //variable
                        if (vh!=null) 
                            exportNumber=vh.fetchVariableAsDouble(aft);                 
                        if (exportNumber==null) exportNumber=new Double(0); //if its still null, replace with 0 to make things work
                    }
            }            
        } else {
            exportNumber=((Double)object);            
        }        
        return exportNumber;
    }
    
    public static Double boolToDouble(boolean b) {
        if (b) return new Double(1);
        return new Double(0);
    }
    
    public static boolean doubleToBool(Double d) {
        return d.intValue()==1;
    }    
    
    /**
     * compute pair
     */
    protected Double unitePair(Object aPre,Character oper,Object aAft) {
        Double preN=convertElementToValue(aPre,getFunctionHolder(),getVarHolder());
        Double aftN=convertElementToValue(aAft,getFunctionHolder(),getVarHolder());
        
        //compute
        switch (oper.charValue()) {
            case '+': return new Double(preN.doubleValue()+aftN.doubleValue());  
            case '-': return new Double(preN.doubleValue()-aftN.doubleValue());  
            case '/': return new Double(preN.doubleValue()/aftN.doubleValue());  
            case '*': return new Double(preN.doubleValue()*aftN.doubleValue()); 
            case '[': return boolToDouble(preN.doubleValue()>=aftN.doubleValue()); 
            case ']': return boolToDouble(preN.doubleValue()<=aftN.doubleValue()); 
            case '>': return boolToDouble(preN.doubleValue()>aftN.doubleValue()); 
            case '<': return boolToDouble(preN.doubleValue()<aftN.doubleValue()); 
            case '!': return boolToDouble(!preN.equals(aftN)); 
            case '=': return boolToDouble(preN.equals(aftN)); 
            case '|': return boolToDouble(doubleToBool(preN) || doubleToBool(aftN)); 
            case '&': return boolToDouble(doubleToBool(preN) && doubleToBool(aftN)); 
            default: return null;
        }
    }
    
    /**
     * function is responsible for replacing common double char operators with simplified one char operators
     */
    protected static String replaceOperators(String str) {
        String s=str.replaceAll("&&","&");
        s=s.replaceAll("\\|\\|","|");
        s=s.replaceAll("==","=");
        s=s.replaceAll("!=","!");
        s=s.replaceAll(">=","]");
        s=s.replaceAll(">=","[");
        return s;
    }
    
    /**
     * solves input string and returns number
     */
    public Double solvePairs(String aStr) {        
        tokenizeInput(replaceOperators(aStr));        
        
        while (getTokenizedElements().size()>1) {
            Iterator i=getTokenizedElements().iterator();
            Object pre=null,aft=null; Character oper=null; 
            while (i.hasNext()) {
                
                if (aft==null) pre=i.next(); else pre=aft;
                Object o=i.next();
                oper=(Character) o;
                aft=i.next();
                if (isOperatorViableForSimplify(oper)) {
                    //simplify is viable here
                    getTokenizedElements().add(getTokenizedElements().indexOf(pre),unitePair(pre,oper,aft));
                    getTokenizedElements().remove(pre);
                    getTokenizedElements().remove(oper);
                    getTokenizedElements().remove(aft);
                    break;
                }
            }
        }
        
        Object o=getTokenizedElements().get(0);
        if (o instanceof Double) return (Double) o;
        else return convertElementToValue(o,getFunctionHolder(),getVarHolder());    }
    
    /**
     * method what defines operator priorities
     * first "row" in created list is always list of all viable operators
     */
    protected List getOperators() {
        if (operators==null) {
            operators=new ArrayList();
            operators.add("+-*/&|!=><[]"); //all operators
            operators.add("+-");   //lowest priority
            operators.add("*/");   //priority raises with index of row inserted here
            operators.add("=![]<>");   
            operators.add("&|");   //max priority
        }
        return operators;
    }
    
    protected List getTokenizedElements() {
        if (tokenizedElements==null) {
            tokenizedElements=new ArrayList();
        }
        return tokenizedElements;        
    }

    protected VariableHolder getVarHolder() {
        return _varHolder;
    }

    /**
     * set variable holder with this method to make system work with variables 
     */
    public void setVarHolder(VariableHolder aVarHolder) {
        _varHolder = aVarHolder;
    }

    protected FunctionHolder getFunctionHolder() {
        return _functionHolder;
    }

    public void setFunctionHolder(FunctionHolder aFunctionHolder) {
        _functionHolder = aFunctionHolder;
    }    
}
